/*
 * StringObject.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.exceptions.ProgrammingError;
import org.scriptme.ecmascript.interpreter.Evaluator;

/**
 * Implemements the EcmaScript String singleton.
 */
public class StringObject extends BuiltinFunctionObject {

	/**
	 * Instantiates a new string object.
	 * 
	 * @param prototype
	 *            the prototype
	 * @param evaluator
	 *            the evaluator
	 */
	private StringObject(ESObject prototype, Evaluator evaluator) {
		super(prototype, evaluator, "String", 1);
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.BuiltinFunctionObject#toString()
	 */
	public String toString() {
		return "<String>";
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.FunctionPrototype#callFunction(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		if (arguments.length == 0) {
			return new ESString("");
		} else {
			return new ESString(arguments[0].toString());
		}
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#doConstruct(org.scriptme.ecmascript.data.ESObject,
	 *      org.scriptme.ecmascript.data.ESValue[])
	 */
	public ESObject doConstruct(ESObject thisObject, ESValue[] arguments)
			throws EcmaScriptException {
		StringPrototype theObject = null;
		ESObject sp = evaluator.getStringPrototype();
		theObject = new StringPrototype(sp, evaluator);
		if (arguments.length > 0) {
			theObject.value = new ESString(arguments[0].toString());
		} else {
			theObject.value = new ESString("");
		}
		return theObject;
	}

	/**
	 * Utility function to create the single String object.
	 * 
	 * @param evaluator
	 *            the Evaluator
	 * @param objectPrototype
	 *            The Object prototype attached to the evaluator
	 * @param functionPrototype
	 *            The Function prototype attached to the evaluator
	 * 
	 * @return the String singleton
	 */
	public static StringObject makeStringObject(Evaluator evaluator,
			ObjectPrototype objectPrototype, FunctionPrototype functionPrototype) {

		StringPrototype stringPrototype = new StringPrototype(objectPrototype,
				evaluator);
		StringObject stringObject = new StringObject(functionPrototype,
				evaluator);

		try {
			// For stringPrototype
			class StringPrototypeToString extends BuiltinFunctionObject {
				StringPrototypeToString(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					return ((StringPrototype) thisObject).value;
				}
			}

			class StringPrototypeValueOf extends BuiltinFunctionObject {
				StringPrototypeValueOf(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					return ((StringPrototype) thisObject).value;
				}
			}

			class StringPrototypeCharAt extends BuiltinFunctionObject {
				StringPrototypeCharAt(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					int pos = 0;
					if (arguments.length > 0) {
						pos = arguments[0].toInt32();
					}
					if (pos >= 0 && pos < str.length()) {
						char c[] = { str.charAt(pos) };
						return new ESString(new String(c));
					} else {
						return new ESString("");
					}
				}
			}

			class StringPrototypeCharCodeAt extends BuiltinFunctionObject {
				StringPrototypeCharCodeAt(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					int pos = 0;
					if (arguments.length > 0) {
						pos = arguments[0].toInt32();
					}
					if (pos >= 0 && pos < str.length()) {
						char c = str.charAt(pos);
						return new ESNumber((double) c);
					} else {
						return new ESNumber(Double.NaN);
					}
				}
			}

			class StringPrototypeIndexOf extends BuiltinFunctionObject {
				StringPrototypeIndexOf(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					int pos = 0;
					if (arguments.length <= 0) {
						return new ESNumber(-1);
					}
					String searched = arguments[0].toString();
					if (arguments.length > 1) {
						pos = arguments[1].toInt32();
					}
					int res = str.indexOf(searched, pos);
					return new ESNumber(res);
				}
			}

			class StringPrototypeLastIndexOf extends BuiltinFunctionObject {
				StringPrototypeLastIndexOf(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					// TODO Implement this function
					/*
					 * String str = thisObject.toString(); int pos =
					 * str.length(); if (arguments.length<=0) { return new
					 * ESNumber(-1); } String searched =
					 * arguments[0].toString(); if (arguments.length>1) { double
					 * p = arguments[1].doubleValue(); if (!Double.isNaN(p)) pos =
					 * arguments[1].toInt32(); } int res =
					 * str.lastIndexOf(searched, pos); return new ESNumber(res);
					 */
					return new ESNumber(-1);
				}
			}

			// This code is replaced by the ReegExp variant when RegExp is
			// loaded
			class StringPrototypeSplit extends BuiltinFunctionObject {
				StringPrototypeSplit(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					ESObject ap = this.evaluator.getArrayPrototype();
					ArrayPrototype theArray = new ArrayPrototype(ap,
							this.evaluator);
					if (arguments.length <= 0) {
						theArray.setSize(1);
						theArray.setElementAt(thisObject, 0);
					} else {
						String sep = arguments[0].toString();
						if (sep.length() == 0) {
							int l = str.length();
							theArray.setSize(l);
							for (int i = 0; i < l; i++) {
								theArray.setElementAt(new ESString(str
										.substring(i, i + 1)), i);
							}
						} else {
							int i = 0;
							int start = 0;
							while (start < str.length()) {
								int pos = str.indexOf(sep, start);
								if (pos < 0)
									pos = str.length();
								// System.out.println("start: " + start + ",
								// pos: " + pos);
								theArray.setSize(i + 1);
								theArray.setElementAt(new ESString(str
										.substring(start, pos)), i);
								start = pos + sep.length();
								i++;
							}
						}
					}
					return theArray;
				}
			}

			class StringPrototypeSubstring extends BuiltinFunctionObject {
				StringPrototypeSubstring(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					int start = 0;
					int end = str.length();
					if (arguments.length > 0) {
						start = arguments[0].toInt32();
					}
					if (start < 0)
						start = 0;
					else if (start > str.length())
						start = str.length();
					if (arguments.length > 1) {
						end = arguments[1].toInt32();
						if (end < 0)
							end = 0;
						else if (end > str.length())
							end = str.length();
					}
					if (start > end) {
						int x = start;
						start = end;
						end = x;
					}
					return new ESString(str.substring(start, end));
				}
			}

			// Added substr from Wendell T. Hicken, not sure if in standard
			class StringPrototypeSubstr extends BuiltinFunctionObject {
				StringPrototypeSubstr(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();
					int start = 0;
					if (arguments.length > 0) {
						start = arguments[0].toInt32();
					}
					int len = Integer.MAX_VALUE;
					if (arguments.length > 1) {
						len = arguments[1].toInt32();
					}

					if (start < 0) {
						// Note: This is how ECMA262 defines substr.
						// IE (Version 5.5) doesn't support negative numbers
						// here,
						// just truncating to zero, like "substring"
						start = str.length() + start;

						if (start < 0)
							start = 0;
					}

					if (len < 0)
						len = 0;
					if (str.length() - start < len)
						len = str.length() - start;

					if (len <= 0) {
						return new ESString("");
					}
					return new ESString(str.substring(start, start + len));
				}
			}

			class StringPrototypeToLowerCase extends BuiltinFunctionObject {
				StringPrototypeToLowerCase(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();

					return new ESString(str.toLowerCase());
				}
			}

			class StringPrototypeToUpperCase extends BuiltinFunctionObject {
				StringPrototypeToUpperCase(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					String str = thisObject.toString();

					return new ESString(str.toUpperCase());
				}
			}

			// For stringObject
			class StringObjectFromCharCode extends BuiltinFunctionObject {
				StringObjectFromCharCode(String name, Evaluator evaluator,
						FunctionPrototype fp) {
					super(fp, evaluator, name, 1);
				}

				public ESValue callFunction(ESObject thisObject,
						ESValue[] arguments) throws EcmaScriptException {
					ESObject sp = this.evaluator.getStringPrototype();
					StringPrototype theObject = new StringPrototype(sp,
							this.evaluator);
					StringBuffer sb = new StringBuffer();
					for (int i = 0; i < arguments.length; i++) {
						char c = (char) (arguments[i].toUInt16());
						sb.append(c);
					}
					theObject.value = new ESString(sb.toString());
					return theObject;
				}
			}

			stringObject.putHiddenProperty("prototype", stringPrototype);
			stringObject.putHiddenProperty("length", new ESNumber(1));
			stringObject.putHiddenProperty("fromCharCode",
					new StringObjectFromCharCode("fromCharCode", evaluator,
							functionPrototype));

			stringPrototype.putHiddenProperty("constructor", stringObject);
			stringPrototype.putHiddenProperty("toString",
					new StringPrototypeToString("toString", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("valueOf",
					new StringPrototypeValueOf("valueOf", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("charAt",
					new StringPrototypeCharAt("charAt", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("charCodeAt",
					new StringPrototypeCharCodeAt("charCodeAt", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("indexOf",
					new StringPrototypeIndexOf("indexOf", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("lastIndexOf",
					new StringPrototypeLastIndexOf("lastIndexOf", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("split",
					new StringPrototypeSplit("split", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("substring",
					new StringPrototypeSubstring("substring", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("substr",
					new StringPrototypeSubstr("substr", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("toLowerCase",
					new StringPrototypeToLowerCase("toLowerCase", evaluator,
							functionPrototype));
			stringPrototype.putHiddenProperty("toUpperCase",
					new StringPrototypeToUpperCase("toUpperCase", evaluator,
							functionPrototype));
		} catch (EcmaScriptException e) {
			e.printStackTrace();
			throw new ProgrammingError(e.getMessage());
		}

		evaluator.setStringPrototype(stringPrototype);

		return stringObject;
	}
}
